﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;

namespace Lesson16
{
    #region 反射

    // 什么是程序集
    // 程序集市有编译器编译得到的，供进一步编译执行的哪个中间产物
    // 在Windows系统中，一般表现为后缀是 .dll（库文件）或者是 .exe (可执行文件)的格式
    // 我们写的代码集合最终会被编译器编译为一个程序集供别人使用


    // 元数据
    // 元数据是描述数据的数据
    // 元数据不仅仅用于程序上，在别的领域也有元数据

    // 程序中的类，类中的函数、变量等等信息就是 程序的 元数据
    // 有关程序以及类型的数据被称为 元数据， 它们保存在程序集中

    // 反射的概念
    // 程序在运行时，可以查看其它程序集或者自身的元数据
    // 一个运行的程序查看本身或者其它程序的元数据的行为就叫做反射

    // 在程序运行时，通过反射可以得到其他程序集或者自身程序集代码的各种信息
    // 类、函数、变量、对象等等，实例化它们、执行它们、操作它们

    // 反射的作用
    // 反射可以在程序编译后获得信息，所以它提高了程序的拓展性和灵活性
    //   1、程序运行时得到所有元数据，包括元数据的特性
    //   2、程序运行时，实例化对象、操作对象
    //   3、程序运行时，创建新对象，用这些对象执行任务

    #endregion

    class Test
    {
        private int i = 1;
        public int j = 10;
        public string str = "floor";

        public Test()
        {
            Console.WriteLine("Test类的构造函数");
        }

        public Test(int i)
        {
            this.i = i;
            Console.WriteLine("Test类的构造函数" + i);
        }

        public Test(int i, string str):this(i)
        {
            this.str = str;
        }

        public void Speak()
        {
            Console.WriteLine("i=" + i);
        }

        public void Speak(int ii)
        {
            Console.WriteLine("ii=" + ii);
        }

    }

    internal class Program
    {
        static void Main(string[] args)
        {
            #region 反射语法相关
            // Type
            // Type(类的信息类)
            // 它是反射功能的基础
            // 它是访问元数组的主要方式
            // 使用Type的常用获取有关类型声明的信息
            // 有关类型的成员，（如构造函数、方法、字段、属性和类的事件）

            // 获取 Type
            //   1、万物之父object中的 GetType方法
            int a = 332;
            Type type1 = a.GetType();
            Console.WriteLine(type1); // System.Int32

            //   2、typeof关键字,传入类名
            Type type2 = typeof(int);
            Console.WriteLine(type2);

            //   3、通过类的名字，也可以获取类型 (注意：类名必须包含命名空间，全类名)
            Type type3 = Type.GetType("System.Int32");
            Console.WriteLine(type3);

            // 得到类的程序集信息
            // 可以通过Type得到类型所在程序集信息
            // Assembly类 就是程序集类
            Console.WriteLine(type1.Assembly);
            Console.WriteLine(type2.Assembly);
            Console.WriteLine(type3.Assembly);

            // 获取类中的所有公共成员
            // 首先要得到Type
            Type t = typeof(Test);
            // 然后得到所有的公共成员
            // 需要引用命名空间 using System.Reflection;
            MemberInfo[] infos = t.GetMembers();
            Console.WriteLine("类中的所有公共成员：");
            // 遍历所有的成员
            for (int i = 0; i<infos.Length; i++)
            {
                Console.WriteLine(infos[i]);    
                Console.WriteLine(infos[i].Name);   // 直接就是名字
            }

            // 获取类的公共构造函数并调用
            // 获取所有构造函数  ConstructorInfo 是构造函数类
            ConstructorInfo[] constructors = t.GetConstructors();
            for (int i = 0; i < constructors.Length; i++)
            {
                Console.WriteLine(constructors[i]); 
                Console.WriteLine(constructors[i].Name); // 构造函数的名字
            }

            // 获取其中一个构造函数，并执行
            // 得到构造函数 传入 Type数组 数组中内容按顺序是参数类型
            // 执行构造函数 传入 object数组 表示按顺序传入的参数
            ConstructorInfo constructor = t.GetConstructor(new Type[0]);  // 表示没有参数的
            // 执行无参构造函数  没有参数 传 null 即可
            //Test obj = (Test)constructor.Invoke(null); // 这一步执行构造函数相当于实例化了类
            Test obj = constructor.Invoke(null) as Test; // 这一步执行构造函数相当于实例化了类
            // 得到类后可以调用类中的方法(非构造方法)，和访问变量
            Console.WriteLine(obj.j);

            // 获取有参构造
            //ConstructorInfo constructor2 = t.GetConstructor(new Type[] { typeof(int) });  // 一个参数的
            ConstructorInfo constructor2 = t.GetConstructor(new Type[] { typeof(int), typeof(string) }); // 2个参数的
            //obj = constructor2.Invoke(new object[] { 100 }) as Test; // 这一步执行构造函数相当于实例化了类
            obj = constructor2.Invoke(new object[] { 100, "floor" }) as Test; // 这一步执行构造函数相当于实例化了类, 传具体参数要和上面传类型一致
            Console.WriteLine(obj.str);
            obj.Speak(); // 调用Speak方法


            // 获取类中所有的公共成员变量
            // 得到所有的成员变量
            FieldInfo[] fields = t.GetFields();
            for (int i = 0; i < fields.Length; i++)
            {
                Console.WriteLine(fields[i]); // 成员变量
                Console.WriteLine(fields[i].Name); // 成员变量的名字
            }

            // 得到指定名称的公共成员变量
            FieldInfo fieldInfoJ = t.GetField("j");
            Console.WriteLine(fieldInfoJ);

            // 通过反射获取和设置对象的值
            // 原来的方式是new对象，然后赋值
            Test test = new Test();
            test.j = 99;
            test.str = "857";
            // 现在通过反射的方式，获取对象某个变量的值
            Console.WriteLine(fieldInfoJ.GetValue(test));  // 打印出 j 的值
            // 通过反射修改对象某个变量的值
            fieldInfoJ.SetValue(test, 100); // 设置 j 的值
            Console.WriteLine(fieldInfoJ.GetValue(test));

            // 获取类中所有的公共成员方法
            // 通过Type类的 GetMethod方法，得到类中的方法， MethodInfo是方法的反射信息
            // 得到所有的方法
            MethodInfo[] methods = t.GetMethods();
            for (int i = 0;i < methods.Length; i++)
            {
                Console.WriteLine(methods[i]); // 方法
                Console.WriteLine(methods[i].Name); // 方法的名字
            }
            // 得到指定名称的方法
            MethodInfo methodInfo = t.GetMethod("Speak", new Type[] { typeof(int) });
            // 调用方法  注意： 如果是静态方法，Invoke的第一个参数传null
            methodInfo.Invoke(test, new object[] {100});

            // Type类的其他方法
            // 得枚举  GetEnumName  GetEnumNames

            // 得事件 GetEvent GetEvents

            // 得接口 GetInterface GetInterfaces

            // 得属性 GetProperty GetProperties


            #endregion

            #region Activator
            // 用于快速实例化对象的类
            // 用于将Type对象快捷实例化为对象
            // 先得到Type
            // 然后 快速实例化一个对象

            // 无参构造
            Type type4 = typeof(Test);
            // 通过Activator类的CreateInstance方法，快速实例化对象
            Test testObj = Activator.CreateInstance(type4) as Test;
            Console.WriteLine(testObj.str);

            // 有参构造
            testObj = Activator.CreateInstance(type4, 1) as Test;   // 有参的话直接传对应类型的值
            Console.WriteLine(testObj.j);

            testObj = Activator.CreateInstance(type4, 1, "floor") as Test;   // 有参的话直接传对应类型的值
            Console.WriteLine(testObj.str);
            #endregion

            #region Assembly
            // 程序集类
            // 主要用来加载其它程序集，加载后才能用Type来使用其它程序集中的信息
            // 如果想要使用不是自己程序集中的内容，需要先加载程序集
            // 比如 dll文件
            // 简单的把库文件看成一种代码仓库，它提供给使用者一些可以直接拿来用的变量、函数、类等

            // 三种加载程序集的函数
            // 一般用来加载再同一文件下的其它程序集
            // Assembly ass1 = Assembly.Load("程序集名称");

            // 一般用来加载不在同一文件下的其它程序集
            // Assembly ass2 = Assembly.LoadFrom("包含程序集清单的文件的名称或路径");
            // Assembly ass3 = Assembly.LoadFile("要加载的文件的完全限定路径");

            // 1、先加载一个指定的程序集
            Assembly assembly = Assembly.LoadFrom("F:\\cSharpUnityPractice\\cSharp\\source\\repos\\Project\\Lesson1\\bin\\Debug\\Lesson1.exe");
            Type[] types = assembly.GetTypes();
            for (int i = 0; i < types.Length; i++)
            {
                Console.WriteLine("类" + types[i]); // 打印出程序集中的所有类，只有一个类 Lesson1.Program
            }

            // 2、再加载程序集中的一个类对象 之后才能使用反射
            Type program = assembly.GetType("Lesson1.Program");
            //program.GetMethod("Main").Invoke(null, new object[] { null }); // 直接调用Main方法
            MemberInfo[] members = program.GetMembers();
            Console.WriteLine("获取类中所有成员");
            for (int i = 0; i < members.Length; i++)
            {
                Console.WriteLine(members[i]); // 打印出程序集中的所有成员
            }
            /*
               Boolean Equals(System.Object)
               Int32 GetHashCode()
               System.Type GetType()
               System.String ToString()
               Void .ctor()                 // 构造函数
            */

            // 实例化对象
            // 通过Activator类的CreateInstance方法，快速实例化对象
            object pobj = Activator.CreateInstance(program);
            // ...

            #endregion

            #region 类库文件的创建
            // 就是创建项目的时候选择 类库(.Framework) 用于创建c#类库(.dll)的项目
            // 创建的文件只能写方法、逻辑，不能执行
            // 可以自己创建一个类库文件，里面写一些方法，然后作为测试使用
            // 要执行生成，会在 /bin 目录下的Debug文件夹里生成一个.dll文件
            // 调用
            Assembly assemblyDll = Assembly.LoadFrom("F:\\cSharpUnityPractice\\cSharp\\source\\repos\\Project\\TestLibrary\\bin\\Debug\\TestLibrary.dll");
            Type[] allType = assemblyDll.GetTypes();
            for (int i = 0; i < allType.Length; i++)
            {
                Console.WriteLine("类" + allType[i]); // 打印出程序集中的所有类
            }
            #endregion
        }
    }
}
